﻿using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;

namespace Devonline.AspNetCore;

public static class TypeExtensions
{
    /// <summary>
    /// 获取对象obj中对于类型 TEntitySet 的第一个引用的外键名称
    /// </summary>
    /// <typeparam name="TEntitySet">主类型参数</typeparam>
    /// <typeparam name="TKey">主键类型参数</typeparam>
    /// <param name="type">包含类型 TEntitySet 引用的对象</param>
    /// <returns>外键名</returns>
    public static string? GetForeignKey<TEntitySet, TKey>(this Type type) where TEntitySet : IEntitySet<TKey>
    {
        var foreignKey = string.Empty;
        var propertyInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.PropertyType.IsFromType<TEntitySet>());
        if (propertyInfo != null)
        {
            foreignKey = propertyInfo.GetAttributeValue<ForeignKeyAttribute, string>(nameof(ForeignKeyAttribute.Name));
        }

        return foreignKey ?? (typeof(TEntitySet).Name + nameof(IEntitySet<TKey>.Id));
    }
    /// <summary>
    /// 获取类型 type 中对于类型 t 的第一个引用的外键名称
    /// </summary>
    /// <param name="type">包含类型 t 引用的对象</param>
    /// <param name="t">类型 t</param>
    /// <returns>外键名</returns>
    public static string? GetForeignKey<TKey>(this Type type, Type t)
    {
        var propertyInfo = type.GetProperties().FirstOrDefault(x => x.PropertyType.IsFromType(t));
        if (propertyInfo != null)
        {
            var foreignKey = propertyInfo.GetCustomAttribute<ForeignKeyAttribute>();
            if (foreignKey != null)
            {
                return foreignKey.Name;
            }

            return propertyInfo.Name + nameof(IEntitySet<TKey>.Id);
        }

        return null;
    }

    /// <summary>
    /// 根据外键属性获取导航属性 
    /// 如获取 Person 的外键 IdCardId 对应的导航属性方式为: idCardIdPropertyInfo.GetNavigationPropertyByForeignKey() => IdCard
    /// 如获取 PersonAdditional 的外键 PersonId 对应的导航属性方式为: personIdPropertyInfo.GetNavigationPropertyByForeignKey() => Person
    /// 外键对于的导航属性为将外键设置为 ForeignKey 特性的属性或者末尾加上 Id 等于外键列名的属性
    /// </summary>
    /// <param name="property">外键属性</param>
    /// <returns>导航属性</returns>
    public static PropertyInfo? GetNavigationPropertyByForeignKey<TKey>(this PropertyInfo property)
    {
        var type = property.DeclaringType;
        if (type == null)
        {
            throw new ArgumentNullException(nameof(property), $"The type of property {property.PropertyType.FullName} has no DeclaringType");
        }

        var prop = type.GetProperties().FirstOrDefault(x => x.GetCustomAttributes<ForeignKeyAttribute>().Any(a => a.Name == property.Name));
        if (prop == null)
        {
            prop = type.GetProperty(property.Name[..^nameof(IEntitySet<TKey>.Id).Length]);
        }

        return prop;
    }
    /// <summary>
    /// 根据导航属性获取外键属性
    /// 如获取 Person 的导航属性 IdCard 对应的外键属性方式为: idCardPropertyInfo.GetForeignKeyByNavigationProperty() => IdCardId
    /// 如获取 PersonAdditional 的导航属性 Person 对应的外键属性方式为: personPropertyInfo.GetForeignKeyByNavigationProperty() => PersonId
    /// 外键对于的导航属性为将外键设置为 ForeignKey 特性的属性或者末尾加上 Id 等于外键列名的属性
    /// </summary>
    /// <param name="property">导航属性</param>
    /// <returns>外键属性</returns>
    public static PropertyInfo? GetForeignKeyByNavigationProperty<TKey>(this PropertyInfo property)
    {
        var type = property.DeclaringType;
        if (type == null)
        {
            throw new ArgumentNullException(nameof(property), $"The type of property {property.PropertyType.FullName} has no DeclaringType");
        }

        var foreignKey = type.GetForeignKey<TKey>(property.PropertyType);
        if (!string.IsNullOrWhiteSpace(foreignKey))
        {
            return type.GetProperty(foreignKey);
        }

        return null;
    }

    /// <summary>
    /// 获取由 NavigableAttribute 指定的导航属性列表
    /// </summary>
    /// <param name="type">要获取NavigableAttribute类型</param>
    /// <returns>对象的NavigableAttribute指定的列</returns>
    public static List<PropertyInfo> GetNavigationProperties<TKey>(this Type type) => type.GetProperties().Where(x => x.HasAttribute<NavigableAttribute>() && (x.PropertyType.IsFromType<IEntitySet<TKey>>() || x.PropertyType.IsFromType<IEnumerable<IEntitySet<TKey>>>())).ToList();
    /// <summary>
    /// 获取由 NavigableAttribute 指定的非导航属性列的值
    /// </summary>
    /// <param name="type">要获取NavigableAttribute类型</param>
    /// <returns>对象的NavigableAttribute指定的列</returns>
    public static PropertyInfo? GetNavigationProperty<TKey>(this Type type) => type.GetProperties().FirstOrDefault(x => x.HasAttribute<NavigableAttribute>() && !x.PropertyType.IsFromType<IEntitySet<TKey>>() && !x.PropertyType.IsFromType<IEnumerable<IEntitySet<TKey>>>());

    /// <summary>
    /// 获取当前模型对象程序集中所有可缓存类型
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="type"></param>
    /// <returns></returns>
    public static List<Type> GetCacheTypes<TKey>(this Type type)
    {
        var types = type.Assembly.GetTypes().Where(x => x.HasAttribute<CacheableAttribute>() && x.IsFromType<IEntitySet<TKey>>()).ToList();
        var cacheTypes = types.Where(x => x.GetProperties().All(a => !a.HasAttribute<NavigableAttribute>())).ToList();
        types = types.Where(x => !cacheTypes.Contains(x)).ToList();
        GetNavigableTypes(types, cacheTypes);
        return cacheTypes;
    }
    /// <summary>
    /// 获取类型的导航属性列表
    /// </summary>
    /// <param name="types"></param>
    /// <param name="cacheTypes"></param>
    private static void GetNavigableTypes(List<Type> types, List<Type> cacheTypes)
    {
        if (types.IsNotNullOrEmpty())
        {
            var temps = new List<Type>();
            foreach (var type in types)
            {
                if (type.GetProperties().Where(x => x.HasAttribute<NavigableAttribute>()).All(x =>
                {
                    var genericType = x.PropertyType.GetGenericType();
                    return genericType != null && cacheTypes.Contains(genericType);
                }))
                {
                    temps.Add(type);
                }
            }

            if (temps.IsNotNullOrEmpty())
            {
                foreach (var type in temps)
                {
                    types.Remove(type);
                    cacheTypes.Add(type);
                }

                GetNavigableTypes(types, cacheTypes);
            }
        }
    }
}
